use std::any::Any;
use std::marker::PhantomData;

use crate::id::Id;
use crate::message::EventResult;
use crate::sequence::ViewMarker;
use crate::view::{Cx, View};
use crate::widget::ChangeFlags;

pub struct Adapt<
    ParentT,
    ParentA,
    ChildT,
    ChildA,
    V: View<ChildT, ChildA>,
    F = fn(&mut ParentT, AdaptThunk<ChildT, ChildA, V>) -> EventResult<ParentA>,
> {
    f: F,
    child: V,
    _phantom_data: PhantomData<(ParentA, ParentT, ChildA, ChildT)>,
}

pub struct AdaptThunk<'a, ChildT, ChildA, V: View<ChildT, ChildA>> {
    child: &'a V,
    state: &'a mut V::State,
    id_path: &'a [Id],
    message: Box<dyn Any>,
}

impl<'a, ChildT, ChildA, V: View<ChildT, ChildA>> AdaptThunk<'a, ChildT, ChildA, V> {
    pub fn call(self, app_state: &mut ChildT) -> EventResult<ChildA> {
        self.child
            .message(self.id_path, self.state, self.message, app_state)
    }
}

impl<ParentT, ParentA, ChildT, ChildA, V: View<ChildT, ChildA>>
    Adapt<ParentT, ParentA, ChildT, ChildA, V>
{
    pub fn new(
        f: fn(&mut ParentT, AdaptThunk<ChildT, ChildA, V>) -> EventResult<ParentA>,
        child: V,
    ) -> Self {
        Self {
            f,
            child,
            _phantom_data: Default::default(),
        }
    }
}

impl<ParentT, ParentA, ChildT, ChildA, V: View<ChildT, ChildA>> View<ParentT, ParentA>
    for Adapt<ParentT, ParentA, ChildT, ChildA, V>
{
    type State = V::State;
    type Element = V::Element;

    fn build(&self, cx: &mut Cx) -> (Id, Self::State, Self::Element) {
        self.child.build(cx)
    }

    fn rebuild(
        &self,
        prev: &Self,
        cx: &mut Cx,
        id: &mut Id,
        state: &mut Self::State,
        element: &mut Self::Element,
    ) -> ChangeFlags {
        self.child.rebuild(&prev.child, cx, id, state, element)
    }

    fn message(
        &self,
        id_path: &[Id],
        state: &mut Self::State,
        event: Box<dyn Any>,
        app_state: &mut ParentT,
    ) -> EventResult<ParentA> {
        let thunk = AdaptThunk {
            child: &self.child,
            state,
            id_path,
            message: event,
        };
        (self.f)(app_state, thunk)
    }
}

impl<ParentT, ParentA, ChildT, ChildA, V: View<ChildT, ChildA>> ViewMarker
    for Adapt<ParentT, ParentA, ChildT, ChildA, V>
{
}
